package com.lagou.edu.factory;

import com.lagou.annotation.Autowired;
import com.lagou.annotation.MyComponent;
import com.lagou.annotation.MyRepository;
import com.lagou.annotation.MyService;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.StringUtils;
import org.springframework.util.SystemPropertyUtils;

import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.*;

/**
 * @author zjy
 * @date: 2020/3/6 4:12 下午
 */
public class BeanFactory {

    /**
     * 存储对象
     */
    private static Map<String,Object> map = new HashMap<>();

    static {
        try{

            //扫描某个包下包含某个自定义注解的类，@MyService和@MyRepository都基于@MyComponent实现的
            Set<Class> scan = scan("com.lagou.edu", MyComponent.class);
            for (Class aClass : scan) {
                // 通过反射技术实例化对象
                Object o = aClass.newInstance();
                String value = "";
                //判断该类是否有自定义MyComponent注解
                MyComponent annotation = (MyComponent)aClass.getAnnotation(MyComponent.class);
                if(annotation != null){
                    value = annotation.value();
                }
                //判断该类是否有自定义MyService注解，有的话获取是否有自定义value
                MyService annotation2 = (MyService)aClass.getAnnotation(MyService.class);
                if(annotation2 != null){
                    value = annotation2.value();
                }
                MyRepository annotation3 = (MyRepository)aClass.getAnnotation(MyRepository.class);
                if(annotation3 != null){
                    value = annotation3.value();
                }
                if(!StringUtils.isEmpty(value)){
                    // 如果自定义了bean的名字,就把自定义的名字存到缓存中
                    map.put(value,o);
                }else {
                    //获取某个类是否有实现某个接口
                    Class[] interfaces = aClass.getInterfaces();
                    if(interfaces.length > 0){
                        // 如果该对象实现了接口就把接口的全限定名作为key，只取了第一个接口名，待优化
                        map.put(interfaces[0].getName(),o);
                    }else{
                        //如果未实现任何接口则以该类的全限定名作为key
                        map.put(aClass.getName(),o);
                    }
                    //最后再以该对象的类的名称为key存一份到缓存中
                    map.put(toLowerCaseFirstOne(aClass.getSimpleName()),o);
                }

            }
            //封装通过Autowired依赖注入的bean
            for(Map.Entry<String, Object> entry : map.entrySet()){
                Object o = entry.getValue();
                //通过反射获取所有属性
                Field[] declaredFields = o.getClass().getDeclaredFields();
                for (Field declaredField : declaredFields) {
                    //获取所有Autowired注解
                    Autowired annotation = declaredField.getAnnotation(Autowired.class);
                    if(annotation != null){
                        declaredField.setAccessible(true);
                        Class<?> type = declaredField.getType();

                        //先以要注入对象的全限定类名获取要注入的bean
                        Object bean = getBean(type.getName());
                        if(bean == null){
                            //如果没获取到，则用自己写的字段名去缓存中获取bean
                            bean = getBean(declaredField.getName());
                        }
                        //通过反射设置依赖注入字段的属性
                        declaredField.set(o, bean);
                    }
                }
            }

        }catch (Exception e){
            e.printStackTrace();
        }

    }
    /**
     * 首字母转小写
     * @param s
     * @return
     */
    public static String toLowerCaseFirstOne(String s){
        if(Character.isLowerCase(s.charAt(0)))
            return s;
        else
            return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();
    }


    // 任务二：对外提供获取实例对象的接口（根据id获取）
    public static  Object getBean(String id) {
        return map.get(id);
    }

    public static void test(){


        InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("classpath://com.lagou.edu");
        System.out.println(resourceAsStream);
    }
    //保存过滤规则要排除的注解
    private final List<TypeFilter> includeFilters = new LinkedList<TypeFilter>();
    private final List<TypeFilter> excludeFilters = new LinkedList<TypeFilter>();

    private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
    private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver);

    public static Set<Class> scan(String[] basePackages,
                                  Class<? extends Annotation>... annotations) {
        BeanFactory cs = new BeanFactory();

        if(annotations != null) {
            for (Class anno : annotations) {
                cs.addIncludeFilter(new AnnotationTypeFilter(anno));
            }
        }

        Set<Class> classes = new HashSet<Class>();
        for (String s : basePackages)
            classes.addAll(cs.doScan(s));
        return classes;
    }

    public static Set<Class> scan(String basePackages, Class<? extends Annotation>... annotations) {
        return BeanFactory.scan(StringUtils.tokenizeToStringArray(basePackages, ",; \t\n"), annotations);
    }

    public final ResourceLoader getResourceLoader() {
        return this.resourcePatternResolver;
    }

    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourcePatternResolver = ResourcePatternUtils
                .getResourcePatternResolver(resourceLoader);
        this.metadataReaderFactory = new CachingMetadataReaderFactory(
                resourceLoader);
    }

    public void addIncludeFilter(TypeFilter includeFilter) {
        this.includeFilters.add(includeFilter);
    }

    public void addExcludeFilter(TypeFilter excludeFilter) {
        this.excludeFilters.add(excludeFilter);
    }

    public void resetFilters(boolean useDefaultFilters) {
        this.includeFilters.clear();
        this.excludeFilters.clear();
    }

    public Set<Class> doScan(String basePackage) {
        Set<Class> classes = new HashSet<Class>();
        try {
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                    + org.springframework.util.ClassUtils
                    .convertClassNameToResourcePath(SystemPropertyUtils
                            .resolvePlaceholders(basePackage))
                    + "/**/*.class";
            Resource[] resources = this.resourcePatternResolver
                    .getResources(packageSearchPath);

            for (int i = 0; i < resources.length; i++) {
                Resource resource = resources[i];
                if (resource.isReadable()) {
                    MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
                    if ((includeFilters.size() == 0 && excludeFilters.size() == 0)
                            || matches(metadataReader)) {
                        try {
                            classes.add(Class.forName(metadataReader
                                    .getClassMetadata().getClassName()));
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        } catch (IOException ex) {
            throw new BeanDefinitionStoreException(
                    "I/O failure during classpath scanning", ex);
        }
        return classes;
    }

    protected boolean matches(MetadataReader metadataReader) throws IOException {
        for (TypeFilter tf : this.excludeFilters) {
            if (tf.match(metadataReader, this.metadataReaderFactory)) {
                return false;
            }
        }
        for (TypeFilter tf : this.includeFilters) {
            if (tf.match(metadataReader, this.metadataReaderFactory)) {
                return true;
            }
        }
        return false;
    }

}
